#include <cassert>
#include "openvip/axi4.h"

namespace ovip {

AXI4MasterPort::AXI4MasterPort(std::string name, xspcomm::XPort &dut_port, xspcomm::XClock &clk,
                               std::string prefix) {
    this->mName     = name;
    this->mPrefix   = prefix;
    this->clk       = &clk;
    this->all_ports = &dut_port.NewSubPort(prefix);
    this->ar_port   = &all_ports->NewSubPort("ar_");
    this->r_port    = &all_ports->NewSubPort("r_");
    this->aw_port   = &all_ports->NewSubPort("aw_");
    this->w_port    = &all_ports->NewSubPort("w_");
    this->b_port    = &all_ports->NewSubPort("b_");

    clk.StepRis([this](u_int64_t c, void *args) {
        this->axi4_step();
    }
    , nullptr, "AXI4MasterPort(" + this->mName + ")_Ris");

    reset_status();
}

AXI4MasterPort::~AXI4MasterPort() {
    delete all_ports;
    delete ar_port;
    delete r_port;
    delete aw_port;
    delete w_port;
    delete b_port;
}

void AXI4MasterPort::reset_status() {
    all_ports->SetZero();
}

bool AXI4MasterPort::send_read_address(uint32_t addr) {
    return send_read_burst(addr, 1);
}

bool AXI4MasterPort::send_read_burst(uint32_t addr, uint8_t len, BurstType burst_type) {
    assert(len >= 1);
    if (ar_busy)
        return false;

    (*ar_port)["valid"]      = 1;
    (*ar_port)["bits_addr"]  = addr;
    (*ar_port)["bits_len"]   = len - 1;
    (*ar_port)["bits_size"]  = AxSIZE;
    (*ar_port)["bits_burst"] = burst_type;
    ar_busy                  = true;
    data_read.clear();

    return true;
}

bool AXI4MasterPort::receive_read_data(uint64_t& data) {
    if (!data_read_valid)
        return false;

    data = data_read.front();
    data_read_valid = false;
    ar_busy = false;

    return true;
}

bool AXI4MasterPort::receive_read_burst(DataVec& data) {
    if (!data_read_valid)
        return false;

    data = data_read;
    data_read_valid = false;
    ar_busy = false;

    return true;
}

bool AXI4MasterPort::write(uint32_t addr, uint64_t data) {
    if (aw_busy || w_busy)
        return false;

    send_write_address(addr, 1);
    send_write_data(DataVec{data});
    return true;
}

bool AXI4MasterPort::write_burst(uint32_t addr, const DataVec& data, BurstType burst_type) {
    if (aw_busy || w_busy)
        return false;

    send_write_address(addr, data.size(), burst_type);
    send_write_data(data);
    return true;
}

bool AXI4MasterPort::send_write_address(uint32_t addr, uint8_t len, BurstType burst_type) {
    assert(len >= 1);
    if (aw_busy)
        return false;

    (*aw_port)["valid"]      = 1;
    (*aw_port)["bits_addr"]  = addr;
    (*aw_port)["bits_len"]   = len - 1;
    (*aw_port)["bits_burst"] = burst_type;
    (*aw_port)["bits_size"]  = AxSIZE;
    aw_busy                  = true;

    return true;
}

bool AXI4MasterPort::send_write_data(const std::vector<uint64_t>& data) {
    if (w_busy) return false;

    for (const auto& item : data)
        data_to_write.push(item);
    (*w_port)["valid"] = 1;
    (*w_port)["bits_data"] = data_to_write.front(); data_to_write.pop();
    (*w_port)["bits_last"] = data_to_write.empty();
    (*w_port)["bits_strb"] = 0xff;
    w_busy                 = true;

    return true;
}

bool AXI4MasterPort::receive_write_response(uint8_t &resp) {
    if (!b_resp_valid)
        return false;

    resp         = this->b_resp;
    b_resp_valid = false;
    aw_busy      = false;
    w_busy       = false;

    return true;
}

void AXI4MasterPort::axi4_step() {
    process_r();
    process_ar();
    process_b();
    process_aw();
    process_w();
}

void AXI4MasterPort::process_ar() {
    if ((*ar_port)["valid"] && (*ar_port)["ready"]) {
        (*ar_port)["valid"] = 0;
        (*r_port)["ready"]  = 1;
    }
}

void AXI4MasterPort::process_r() {
    if ((*r_port)["valid"] && (*r_port)["ready"]) {
        data_read.push_back((*r_port)["bits_data"]);
        if ((*r_port)["bits_last"]) {
            data_read_valid = true;
            (*r_port)["ready"] = 0;
        }
    }
}

void AXI4MasterPort::process_aw() {
    if ((*aw_port)["valid"] && (*aw_port)["ready"]) {
        (*aw_port)["valid"] = 0;
        if (w_busy && !(*w_port)["valid"])
            (*b_port)["ready"] = 1;
    }
}

void AXI4MasterPort::process_b() {
    if ((*b_port)["valid"] && (*b_port)["ready"]) {
        b_resp = (*b_port)["bits_resp"];
        b_resp_valid = true;
        (*b_port)["ready"] = 0;
    }
}

void AXI4MasterPort::process_w() {
    if ((*w_port)["valid"] && (*w_port)["ready"]) {
        if (data_to_write.empty()) {
            (*w_port)["valid"] = 0;
            if (aw_busy && !(*aw_port)["valid"])
                (*b_port)["ready"] = 1;
        } else {
            (*w_port)["bits_data"] = data_to_write.front(); data_to_write.pop();
            (*w_port)["bits_last"] = data_to_write.empty();
        }
    }
}

}
